package com.tomorrow.runner.data;

import android.annotation.SuppressLint;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.OperationApplicationException;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.util.SparseArray;

import com.tomorrow.runner.data.model.ActivityDataModel;
import com.tomorrow.runner.data.model.ActivityModel;
import com.tomorrow.runner.data.model.UserModel;

import java.util.ArrayList;
import java.util.HashMap;

/**
 * cn.sucun.android.server.RunProvider
 * 
 * Instance subContentProvider, according to the uri, get the subProvider
 * match,according match get sub provider Object
 * 
 * @author Jiangchangzheng <br>
 * 
 *         create at 2014-8-13 上午10:44:34
 */
public class RunProvider extends AbsContentProvider {

	private static final String URI_PATH_CALL = "call";

	private static final int BASE_MATCH = UriMatcher.NO_MATCH + 1;
	private static final int CALL_MATCH = BASE_MATCH;

	private static Uri sContentUri = null;
	private static Uri sCallUri = null;

	public static Uri getContentUri() {
		if (sContentUri == null) {
			String authority = getAuthority();
			sContentUri = Uri.parse(ContentResolver.SCHEME_CONTENT + "://"
					+ authority);
		}
		return sContentUri;
	}

	/**
	 * 
	 * @return content://{authority}/call
	 */
	public static Uri getCallUri() {
		if (sCallUri == null) {
			sCallUri = Uri.withAppendedPath(getContentUri(), URI_PATH_CALL);
		}
		return sCallUri;
	}

	private AbsSubProvider<RunProvider>[] mProviders;

	@SuppressWarnings("unchecked")
	private AbsSubProvider<RunProvider>[] getProviders(String authority) {
		if (mProviders == null) {
			mProviders = new AbsSubProvider[] {
					new SimpleProvider(this, authority, UserModel.class),
					new SimpleProvider(this, authority, ActivityModel.class),
					new SimpleProvider(this, authority, ActivityDataModel.class)
					};
			for (AbsSubProvider<RunProvider> provider : mProviders) {
				provider.onCreate();
			}
		}
		return mProviders;
	}

	public UriMatcher mURIMatcher;
	private SparseArray<SQLiteOpenHelper> mOpenHelpers;
	private SparseArray<AbsSubProvider<RunProvider>> mMatchProviders;
	private SparseArray<AbsSubProvider<RunProvider>> mBatchProviders;
	private HashMap<String, AbsSubProvider<RunProvider>> mCallProviders;

	@Override
	public boolean onCreate() {
		mMatchProviders = new SparseArray<>();
		mBatchProviders = new SparseArray<>();
		mOpenHelpers = new SparseArray<>();
		mCallProviders = new HashMap<>();

		String authority = getAuthority();

		AbsSubProvider<RunProvider>[] providers = getProviders(authority);

		if (mURIMatcher == null) {
			mURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
			mURIMatcher.addURI(authority, URI_PATH_CALL, CALL_MATCH);
			// offset is the provider match number
			int offset = CALL_MATCH + 1;
			for (AbsSubProvider<RunProvider> provider : providers) {
				int count = provider.registerMatch(mURIMatcher, offset);
				if (count > 0) {
					int end = offset + count;
					for (int i = offset; i < end; i++) {
						mMatchProviders.put(i, provider);
					}
					offset = end;
				}
			}
		}

		for (AbsSubProvider<RunProvider> provider : providers) {
			provider.registerSQLiteOpenHelper(mOpenHelpers);
			String[] calls = provider.getCallMatched();
			if (calls != null) {
				for (String call : calls) {
					mCallProviders.put(call, provider);
				}
			}

			int[] matchs = provider.getMatchForApplyBatch();
			if (matchs != null) {
				for (int match : matchs) {
					mBatchProviders.put(match, provider);
				}
			}
		}
		return true;
	}

	/**
	 * shutdown each sub provider
	 */
	public void shutdown() {
		if (mProviders != null) {
			for (AbsSubProvider<RunProvider> provider : mProviders) {
				provider.shutdown();
			}
			mProviders = null;
		}

		if (mOpenHelpers != null) {
			int size = mOpenHelpers.size();
			for (int i = 0; i < size; i++) {
				mOpenHelpers.valueAt(i).close();
			}
			mOpenHelpers.clear();
			mOpenHelpers = null;
		}

		if (mCallProviders != null) {
			mCallProviders.clear();
			mCallProviders = null;
		}
	}

	@SuppressLint("NewApi")
	@Override
	protected void finalize() throws Throwable {
		shutdown();
		super.finalize();
	}

	/**
	 * get provider from mCallProvider according to the method,like
	 * CALL_REFRESH_PATH
	 */
	@Override
	public Bundle call(String method, String arg, Bundle extras) {
		AbsSubProvider<RunProvider> provider = mCallProviders.get(method);
		if (provider == null) {
			throw new IllegalArgumentException("Unknown method: " + method);
		}
		return provider.call(method, arg, extras);
	}

	@Override
	protected boolean isCall(Uri uri) {
		return mURIMatcher.match(uri) == CALL_MATCH;
	}

	@Override
	public String getType(@NonNull Uri uri) {
		final int match = mURIMatcher.match(uri);

		AbsSubProvider<RunProvider> provider = mMatchProviders.get(match);
		if (provider == null) {
			throw new IllegalArgumentException("Unknown URI: " + uri);
		}

		return provider.getType(uri, match);
	}

	@Override
	public Cursor query(@NonNull Uri uri, String[] projection, String selection,
						String[] selectionArgs, String sortOrder) {
		final int match = mURIMatcher.match(uri);

		AbsSubProvider<RunProvider> provider = mMatchProviders.get(match);
		if (provider == null) {
			throw new IllegalArgumentException("Unknown URI: " + uri);
		}

		SQLiteOpenHelper openHelper = mOpenHelpers.get(match);
		SQLiteDatabase db = openHelper == null ? null : openHelper
				.getReadableDatabase();
		return provider.query(db, match, uri, projection, selection,
				selectionArgs, sortOrder);
	}

	@Override
	public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) {
		final int match = mURIMatcher.match(uri);

		AbsSubProvider<RunProvider> provider = mMatchProviders.get(match);
		if (provider == null) {
			throw new IllegalArgumentException("Unknown URI: " + uri);
		}

		SQLiteOpenHelper openHelper = mOpenHelpers.get(match);
		SQLiteDatabase db = openHelper == null ? null : openHelper
				.getWritableDatabase();
		return provider.delete(db, match, uri, selection, selectionArgs);
	}

	@Override
	public int update(@NonNull Uri uri, ContentValues values, String selection,
					  String[] selectionArgs) {
		final int match = mURIMatcher.match(uri);

		AbsSubProvider<RunProvider> provider = mMatchProviders.get(match);
		if (provider == null) {
			throw new IllegalArgumentException("Unknown URI: " + uri);
		}

		SQLiteOpenHelper openHelper = mOpenHelpers.get(match);
		SQLiteDatabase db = openHelper == null ? null : openHelper
				.getWritableDatabase();
		return provider
				.update(db, match, uri, values, selection, selectionArgs);
	}

	@Override
	public Uri insertRow(Uri uri, ContentValues values) {
		final int match = mURIMatcher.match(uri);

		AbsSubProvider<RunProvider> provider = mMatchProviders.get(match);
		if (provider == null) {
			throw new IllegalArgumentException("Unknown URI: " + uri);
		}

		SQLiteOpenHelper openHelper = mOpenHelpers.get(match);
		SQLiteDatabase db = openHelper == null ? null : openHelper
				.getWritableDatabase();
		return provider.insert(db, match, uri, values);
	}

	@Override
	public int bulkInsert(@NonNull Uri uri,@NonNull ContentValues[] values) {
		final int match = mURIMatcher.match(uri);

		AbsSubProvider<RunProvider> provider = mMatchProviders.get(match);
		if (provider == null) {
			throw new IllegalArgumentException("Unknown URI: " + uri);
		}

		if (!provider.isSupportBulkInsert(match)) {
			return super.bulkInsert(uri, values);
		}
		SQLiteOpenHelper openHelper = mOpenHelpers.get(match);
		SQLiteDatabase db = openHelper == null ? null : openHelper
				.getWritableDatabase();
		return provider.bulkInsert(db, match, uri, values);
	}

	@Override
	public @NonNull ContentProviderResult[] applyBatch(@NonNull ArrayList<ContentProviderOperation> operations)
			throws OperationApplicationException {
		if (mBatchProviders.size() <= 0) {
			return super.applyBatch(operations);
		}

		final int size = operations.size();
		HashMap<ContentProviderOperation, Integer> indexMap = new HashMap<>();

		ArrayList<ContentProviderOperation> unbatchOpers = new ArrayList<>();
		SparseArray<ArrayList<ContentProviderOperation>> matchedOpers = new SparseArray<>();
		for (int i = 0; i < size; i++) {
			ContentProviderOperation oper = operations.get(i);
			indexMap.put(oper, i);
			Uri uri = oper.getUri();
			int match = mURIMatcher.match(uri);
			AbsSubProvider<RunProvider> provider = mBatchProviders.get(match);

			if (provider == null) {
				unbatchOpers.add(oper);
				continue;
			}

			ArrayList<ContentProviderOperation> list = matchedOpers.get(match);
			if (list == null) {
				list = new ArrayList<>();
				matchedOpers.put(match, list);
			}
			list.add(oper);
		}

		ContentProviderResult[] result = new ContentProviderResult[size];
		final int unbatchSize = unbatchOpers.size();
		if (unbatchSize > 0) {
			ContentProviderResult[] unbatchResult = super
					.applyBatch(unbatchOpers);

			for (int i = 0; i < unbatchSize; i++) {
				result[indexMap.get(unbatchOpers.get(i))] = unbatchResult[i];
			}
		}

		final int matchSize = matchedOpers.size();
		for (int i = 0; i < matchSize; i++) {
			int match = matchedOpers.keyAt(i);
			ArrayList<ContentProviderOperation> opers = matchedOpers.valueAt(i);
			AbsSubProvider<RunProvider> provider = mBatchProviders.get(match);

			SQLiteOpenHelper openHelper = mOpenHelpers.get(match);
			SQLiteDatabase db = openHelper == null ? null : openHelper
					.getWritableDatabase();

			ContentProviderResult[] batchResult = provider.applyBatch(db,
					match, opers);
			int operSize = opers.size();
			for (int j = 0; j < operSize; j++) {
				result[indexMap.get(opers.get(j))] = batchResult[j];
			}
		}

		return result;
	}
}
